﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Pixysoft.Framework.Noebe.Utility;
using System.Data;
using Pixysoft.Framework.XSchEmA;
using Pixysoft.Framework.Noebe.Command;
using Pixysoft.Tools;
using Pixysoft.Collections;

namespace Pixysoft.Framework.Noebe.Core
{
    /// <summary>
    /// 由于dotnet的command不支持多线程，会导致socket传输出现并发，因此无法使用这个。
    /// </summary>
    class ConnectionPool
    {
        private readonly ReaderWriterLock locker = new ReaderWriterLock();

        private ConnectionPoolPerformance performance = new ConnectionPoolPerformance();

        private CircleQueue<IProxyConnection> availableQueue = new CircleQueue<IProxyConnection>();

        private List<IProxyConnection> removeList = new List<IProxyConnection>();

        ICommandGenerator generator = null;

        NoebeConfiguration config = null;

        public ConnectionPool(NoebeConfiguration config, ICommandGenerator generator)
        {
            this.config = config;

            this.generator = generator;
        }

        public IDbConnection GetConnection(string connStr)
        {
            DoRequest();

            // if connection pool is disabled, return new one.

            if (!config.IsConnectionPoolEnabled)
            {
                return generator.GetConnection(connStr);
            }

            IProxyConnection proxyConnection = null;

            //if connection is EMTPY, create new one.

            if (availableQueue.Size() == 0)
            {
                locker.AcquireWriterLock(Timeout.Infinite);

                try
                {
                    // double-check lock

                    if (availableQueue.Size() == 0)
                    {

                        LoggerHelper.Debug("connection pool has no connection. create new one.");

                        proxyConnection = this.CreateProxyConnection(connStr);

                        return proxyConnection.GetConnection();

                    }
                }
                finally
                {
                    locker.ReleaseWriterLock();
                }
            }

            // search one connection, NO disposed is OK.

            do
            {
                proxyConnection = this.GetNextAvailableConnection();

                if (proxyConnection == null)
                {
                    LoggerHelper.Debug("no available connection in pool, break from loop.");

                    break;
                }

                if (!proxyConnection.TryDoRequest())
                {
                    LoggerHelper.Debug("next connection is disposed. rmove it and loop again.");

                    RemoveAvailableConnection(proxyConnection);

                    continue;
                }

                break;
            }
            while (availableQueue.Size() > 0);

            // if no connection create new one and return

            if (proxyConnection == null)
            {
                LoggerHelper.Debug("connection is not EMPTY, but no available connection. create new one.");

                proxyConnection = this.ThreadSafeCreateProxyConnection(connStr);

                return proxyConnection.GetConnection();
            }

            // got one connection AVAILABLE / BUSY

            if (IsOverloaded(this.config, proxyConnection.GetStatementSize(), availableQueue.Size()))
            {
                // in multithread mode, create connection would pending. so need double-check

                locker.AcquireWriterLock(Timeout.Infinite);

                try
                {
                    if (IsOverloaded(this.config, proxyConnection.GetStatementSize(), availableQueue.Size()))
                    {
                        LoggerHelper.Debug("connection is overloaded, create new one. ts=" + proxyConnection.GetStatementSize() + ";tc="
                                + availableQueue.Size());

                        //force to create new connection

                        proxyConnection.CancelRequest();

                        proxyConnection = this.CreateProxyConnection(connStr);

                        return proxyConnection.GetConnection();
                    }
                }
                finally
                {
                    locker.ReleaseWriterLock();
                }
            }

            //if over available, Add to Remove list

            if (TryIsOverAvailable(proxyConnection.GetStatementSize(), availableQueue.Size()))
            {
                locker.AcquireWriterLock(Timeout.Infinite);

                try
                {
                    if (IsOverAvailable(proxyConnection.GetStatementSize(), availableQueue.Size()))
                    {
                        LoggerHelper.Debug("connection is over available, set as REMOVED status, and Remove from pool. id=" + proxyConnection.ConnectionId
                                + ";ts=" + proxyConnection.GetStatementSize() + ";tc=" + availableQueue.Size());

                        proxyConnection.SetRemoved();

                        RemoveAvailableConnection(proxyConnection);

                        PutRemovedConnection(proxyConnection);

                        return proxyConnection.GetConnection();
                    }
                }
                finally
                {
                    locker.ReleaseWriterLock();
                }
            }

            //return connection

            LoggerHelper.Debug("connection is available, reuse it. ts=" + proxyConnection.GetStatementSize() + ";tc=" + availableQueue.Size());

            return proxyConnection.GetConnection();

        }

        private void DoRequest()
        {
            locker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                this.performance.IncreaseTotalRequestTimes();
            }
            finally
            {
                locker.ReleaseReaderLock();
            }
        }

        public void PutRemovedConnection(IProxyConnection conn)
        {
            removeList.Add(conn);
        }

        public bool ThreadSafeRemoveRemovedConnection(IProxyConnection conn)
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                LoggerHelper.Debug("Remove connection from removed list, id=" + conn.ConnectionId + ";requestcount=" + conn.GetRequestTimes());

                this.performance.IncreaseTotalRemovedTimes();

                this.performance.AddTotalRemovedRequestTimes(conn.GetRequestTimes());

                return removeList.Remove(conn);
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        private IProxyConnection GetNextAvailableConnection()
        {
            IProxyConnection connection = this.availableQueue.Poll();

            return connection;
        }

        private IProxyConnection CreateProxyConnection(string connStr)
        {
            IDbConnection realConnection = generator.GetConnection(connStr);

            IProxyConnection proxyConnection = new ProxyConnection(this.performance.NextConnectionId().ToString(), realConnection, this);

            proxyConnection.TryDoRequest();

            availableQueue.Append(proxyConnection.ConnectionId, proxyConnection);

            LoggerHelper.Debug("create new connection and Append to pool, Size = " + availableQueue.Size() + "; connectionid = "
                    + proxyConnection.ConnectionId);

            return proxyConnection;
        }

        private IProxyConnection ThreadSafeCreateProxyConnection(string connStr)
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                IProxyConnection conn = CreateProxyConnection(connStr);

                return conn;
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        private bool RemoveAvailableConnection(IProxyConnection conn)
        {
            return this.availableQueue.Remove(conn.ConnectionId) != null;
        }

        private bool TryIsOverAvailable(int totalStatementSize, int totalConnectionSize)
        {
            return totalConnectionSize > 1 && (totalStatementSize + 1) < totalConnectionSize;
        }

        private bool IsOverAvailable(int totalStatementSize, int totalConnectionSize)
        {
            if (!TryIsOverAvailable(totalStatementSize, totalConnectionSize))
            {
                return false;
            }

            DateTime currentLazytime = GlobalTimer.Instance.GetGlobalTime();

            if (this.performance.GetLastLazytime() == DateTime.MinValue)
            {
                this.performance.SetLastLazytime(currentLazytime);

                return false;
            }

            //1000 * 10
            if (this.performance.GetLastLazytime().AddMilliseconds(StringHelper.interval_lazytime) > currentLazytime)
            {
                return false;
            }

            this.performance.SetLastLazytime(currentLazytime);

            return true;
        }

        private bool IsOverloaded(NoebeConfiguration config, int totalStatementSize, int totalConnectionSize)
        {
            double tc = totalConnectionSize;

            double ts = tc * tc * (config.MaxCsRate - config.MinCsRate) / config.MaxConnection + config.MaxCsRate * tc;

            return totalStatementSize > ts;
        }

        public void Purge()
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                //purge removed connections

                foreach (IProxyConnection conn in this.removeList.ToArray())
                {
                    if (conn.GetStatementSize() != 0)
                    {
                        continue;
                    }

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }

                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                int availableSize = availableQueue.Size();

                List<IProxyConnection> rlist = new List<IProxyConnection>();

                for (int i = 0; i < availableSize; i++)
                {
                    IProxyConnection conn = availableQueue.Poll();

                    if (!conn.IsTimeout())
                    {
                        continue;
                    }

                    rlist.Add(conn);
                }

                foreach (IProxyConnection conn in rlist)
                {
                    availableQueue.Remove(conn.ConnectionId);

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }

                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        public void Shutdown()
        {
            locker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                while (!availableQueue.IsEmpty())
                {
                    IProxyConnection conn = availableQueue.PollAndRemove();

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }

                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                availableQueue.ClearAll();

                foreach (IProxyConnection conn in removeList.ToArray())
                {

                    try
                    {
                        conn.ReallyClose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }


                    try
                    {
                        conn.ReallyDispose();
                    }
                    catch (Exception e)
                    {
                        LoggerHelper.Fatal(e);
                    }
                }

                removeList.Clear();
            }
            finally
            {
                locker.ReleaseWriterLock();
            }

        }

        public ConnectionPoolSnapshot CreateSnapshot()
        {
            ConnectionPoolSnapshot admin = new ConnectionPoolSnapshot();
            admin.SetConnectionLastLazyTime(this.performance.GetLastLazytime());
            admin.SetConnectionAvailableSize(this.availableQueue.Size());
            admin.SetConnectionNextConnectionId(this.performance.GetNextConnectionId());
            admin.SetConnectionRemovedQueueSize(this.removeList.Count);
            admin.SetTotalRequestTime(this.performance.GetTotalRequestTimes());
            admin.SetConnectionRemovedCount(this.performance.GetTotalRemovedTimes());
            admin.SetRequestRemovedCount(this.performance.GetTotalRemovedRequestTimes());

            locker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                for (int i = 0; i < this.availableQueue.Size(); i++)
                {
                    admin.GetAvailableConnections().Add(this.availableQueue.Poll().CreateShapshot());
                }

                foreach (IProxyConnection conn in this.removeList.ToArray())
                {
                    admin.GetRemovedConnections().Add(conn.CreateShapshot());
                }
            }
            finally
            {
                locker.ReleaseReaderLock();
            }

            return admin;
        }
    }
}
